For AHI version 4.20. Document version 4.11 (1999-03-28).
Copyright (C) 1994-1999 Martin Blom
The latest release of AHI can always be found at http://www.lysator.liu.se/~lcs/ahi.html.
This document was written in order to make it easier for developers to understand and use AHI in their own productions, and write Software That Works(TM).
ahi.device
has two different API's; one library-like function
interface (low-level), and one "normal" device interface (high-level).
Each of them serves different purposes. The low-level interface is
targeting music players, games and real-time applications. The high-level
interface is targeting applications that just want to have a sample played,
play audio streams or record samples as easily as possible.
As with everything else, it is important that you chose the right tool for the job--you'll only get frustrated otherwise.
Not everything about AHI is documented here; for more information, see AHI User's Guide and the autodocs.
Copyright (C) 1994-1999 Martin Blom
AHI is available as freeware. That is, it may be freely distributed in unmodified form with no changes what so ever, but you may not charge more than a nominal fee covering distribution costs. However, donations are welcome (see AHI User's Guide).
If you use this software in a commercial or shareware product, please consider giving the author (see section The Author)---and preferably each one of the contributors too (see AHI User's Guide)---an original or registered version of your work. Should you want to distribute the AHI software with your own product, there is really nothing to consider, is it?
If you wish to distribute this software with a hardware product, contact the author (see section The Author). Distribution of AHI with hardware products is not free.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The author can be reached at the following addresses:
Following are some general definitions of terms that are used in this document.
The device has, in addition to the usual I/O request protocol, a set of functions that allows the programmer to gain full control (at least as much as possible with device independence) over the audio hardware. The advantages are low overhead and much more advanced control over the playing sounds. The disadvantages are greater complexity and only one user per sound card.
If you want to play music or sound effects for a game, record in high quality or want to do realtime effects, this is the API to use.
It's really simple. If I tell you to check return values, check sample
types when recording, not to trash d2-d7/a2-a6 in hooks, or not to call
AHI_ControlAudio()
with the AHIC_Play
tag from interrupts or
hooks, you do as you are told.
The AHIBase
structure is private, so are the sub libraries' library
base structures. Don't try to be clever.
The implementation of the database is private, and may change any time.
ahi.device
provides functions access the information in the database
(AHI_NextAudioID()
, AHI_GetAudioAttrsA()
and
AHI_BestAudioIDA()
).
All user hooks must follow normal register conventions, which means that d2-d7 and a2-a6 must be preserved. They may be called from an interrupt, but you cannot count on that; it can be your own process or another process. Don't assume the system is in single-thread mode. Never spend much time in a hook, get the work done as quick as possible and then return.
The AHIAudioCtrl
structure may not be shared with other
tasks/threads. The task that called AHI_AllocAudioA()
must do all
other calls too (except those callable from interrupts).
Only calls specifically said to be callable from interrupts may be called
from user hooks or interrupts. Note that AHI_ControlAudioA()
has
some tags that must not be present when called from an interrupt.
Most audio drivers need multitasking to be turned on to function properly. Don't turn it off while using the device.
ahi.device
For Low-level Access
Not too hard. Just open ahi.device
unit AHI_NO_UNIT
and
initialize AHIBase
. After that you can access all the functions of
the device just as if you had opened a standard shared library.
Assembler
For the assembler programmer there are two handy macros: OPENAHI
and CLOSEAHI
. Here is a small example how to use them:
OPENAHI 4 ;Open at least version 4. lea _AHIBase(pc),a0 move.l d0,(a0) beq error ; AHI's functions can now be called as normal library functions: move.l _AHIBase(pc),a6 moveq #AHI_INVALID_ID,d0 jsr _LVOAHI_NextAudioID(a6) error: CLOSEAHI rts
Note that you have to execute the CLOSEAHI
macro even if
OPENAHI
failed!
C
For the C programmer, here is how it should be done:
struct Library *AHIBase; struct MsgPort *AHImp=NULL; struct AHIRequest *AHIio=NULL; BYTE AHIDevice=-1; if(AHImp = CreateMsgPort()) { if(AHIio = (struct AHIRequest *) CreateIORequest( AHImp, sizeof(struct AHIRequest))) { AHIio->ahir_Version = 4; /* Open at least version 4. */ if(!(AHIDevice = OpenDevice(AHINAME, AHI_NO_UNIT, (struct IORequest *) AHIio, NULL))) { AHIBase = (struct Library *) AHIio->ahir_Std.io_Device; // AHI's functions can now be called as normal library functions: AHI_NextAudioID(AHI_INVALID_ID); CloseDevice((struct IORequest *) AHIio); AHIDevice = -1; } DeleteIORequest((struct IORequest *) AHIio); AHIio = NULL; } DeleteMsgPort(AHImp); AHImp = NULL; }
If you wish to call any other function than
AHI_AllocAudioRequestA()
AHI_AudioRequestA()
AHI_BestAudioIDA()
AHI_FreeAudioRequest()
AHI_GetAudioAttrsA()
AHI_NextAudioID()
AHI_SampleFrameSize()
...you have to allocate the actual sound hardware. This is done with
AHI_AllocAudioA()
. AHI_AllocAudioA()
returns an
AHIAudioCtrl
structure, or NULL
if the hardware could not be
allocated. The AHIAudioCtrl
structure has only one public field,
ahiac_UserData
. This is unused by AHI and you may store
anything you like here.
If AHI_AllocAudioA()
fails it is important that you handle the
situation gracefully.
When you are finished playing or recording, call AHI_FreeAudio()
to
deallocate the hardware and other resources allocated by
AHI_AllocAudioA()
. AHI_FreeAudio()
also deallocates all
loaded sounds (see section Declaring Sounds).
AHI_AllocAudioA()
Tags
AHI_AllocAudioA()
takes several tags as input.
AHIA_AudioID
AHI_DEFAULT_ID
, which is the user's default fallback ID.
In most cases you should ask the user for an ID code (with
AHI_AudioRequestA()
) and then store the value in your settings file.
AHIA_MixFreq
AHI_GetAudioAttrsA()
. If omitted or
AHI_DEFAULT_FREQ
, the user's preferred fallback frequency will be
used. In most cases you should ask the user for a frequency (with
AHI_AudioRequestA()
) and then store the value in your settings file.
AHIA_Channels
AHIA_Sounds
AHIA_SoundFunc
AHISoundMessage
structure as message.
AHISoundMessage->ahism_Channel
indicates which channel the sound
that caused the hook to be called is played on.
AHIA_PlayerFunc
AHI_GetAudioAttrsA()
in the autodocs,
the AHIDB_Realtime
tag).
AHIA_PlayerFreq
PlayerFunc
will be called. This must be specified if
AHIA_PlayerFunc
is! It is suggested that you keep the frequency
below 100-200 Hz. Since the frequency is a fixpoint number
AHIA_PlayerFreq
should be less than 13107200 (that's 200 Hz).
AHIA_MinPlayerFreq
AHIA_PlayerFreq
) you will use. You should
always supply this if you are using the device's interrupt feature!
AHIA_MaxPlayerFreq
AHIA_PlayerFreq
) you will use. You should
always supply this if you are using the device's interrupt feature!
AHIA_RecordFunc
AHI_ControlAudioA()
). It is important that you always check the
format of the sampled data, and ignore it if you can't parse it. Since
this hook may be called from an interrupt, it is not legal to directly
Write()
the buffer to disk. To record directly to harddisk you
have to copy the samples to another buffer and signal a process to save it.
To find out the required size of the buffer, see
AHI_GetAudioAttrsA()
in the autodocs, the
AHIDB_MaxRecordSamples
tag.
AHIA_UserData
ahiac_UserData
field. You do not have
to use this tag to change ahiac_UserData
, you may write to it
directly.
Before you can play a sample array, you must AHI_LoadSound()
it.
Why? Because if AHI knows what kind of sounds that will be played
later, tables and stuff can be set up in advance. Some drivers may even
upload the samples to the sound cards local RAM and play all samples from
there, drastically reducing CPU and bus load.
You should AHI_LoadSound()
the most important sounds first, since
the sound cards RAM may not be large enough to hold all your sounds.
AHI_LoadSound()
also associates each sound or sample array with a
number, which is later used to refer to that particular sound.
There are 2 types of sounds, namely AHIST_SAMPLE
and
AHIST_DYNAMICSAMPLE
.
AHIST_SAMPLE
AHIST_DYNAMICSAMPLE
size = samples * Fs / Fmwhere samples is the value returned from
AHI_GetAudioAttrsA()
when
called with the AHIDB_MaxPlaySamples
tag, Fs is the highest
frequency the sound will be played at and Fm is the actual mixing frequency
(AHI_ControlAudioA()/AHIC_MixFreq_Query
).
The samples can be in one of four different formats, named AHIST_M8S
,
AHIST_S8S
, AHIST_M16S
, and AHIST_S16S
.
AHIST_M8S
AHIST_S8S
AHIST_M16S
AHIST_S16S
If you know that you won't use a sound anymore, call
AHI_UnloadSound()
. AHI_FreeAudio()
will also do that for you
for any sounds left when called.
There is no need to place a sample array in Chip memory, but it
must not be swapped out! Allocate your sample memory with the
MEMF_PUBLIC
flag set. If you wish to have your samples in virtual
memory, you have to write a double-buffer routine that copies a chunk of
memory to a MEMF_PUBLIC
buffer. The SoundFunc should signal a
task to do the transfer, since it may run in supervisor mode (see
AHI_AllocAudioA()
).
After you have allocated the sound hardware and declared all your sounds,
you're ready to start playback. This is done with a call to
AHI_ControlAudioA()
, with the AHIC_Play
tag set to
TRUE
. When this function returns the PlayerFunc (see
AHI_AllocAudioA()
) is active, and the audio driver is feeding
silence to the sound hardware.
All you have to do now is to set the desired sound, it's frequency and
volume. This is done with AHI_SetSound()
, AHI_SetFreq()
and
AHI_SetVol()
. Make sure the AHISF_IMM
flag is set for all
these function's flag argument. And don't try to modify a channel
that is out of range! If you have allocated 4 channels you may only modify
channels 0-3.
The sound will not start until both AHI_SetSound()
and
AHI_SetFreq()
has been called. The sound will play even if
AHI_SetVol()
was not called, but it will play completely silent. If
you wish to temporary stop a sound, set its frequency to 0. When you
change the frequency again, the sound will continue where it were.
When the sound has been started it will play to the end and then repeat.
In order to play a one-shot sound you have use the AHI_PlayA()
function, or install a sound interrupt using the AHIA_SoundFunc
tag
with AHI_AllocAudioA()
. For more information about using sound
interrupts, see below.
A little note regarding AHI_SetSound()
: Offset is the first
sample that will be played, both when playing backwards and forwards. This
means that if you call AHI_SetSound()
with offset 0 and
length 4, sample 0,1,2 and 3 will be played. If you call
AHI_SetSound()
with offset 3 and length -4,
sample 3,2,1 and 0 will be played.
Also note that playing very short sounds will be very CPU intensive, since there are many tasks that must be done each time a sound has reached its end (like starting the next one, calling the SoundFunc, etc.). Therefore, it is recommended that you "unroll" short sounds a couple of times before you play them. How many times you should unroll? Well, it depends on the situation, of course, but try making the sound a thousand samples long if you can. Naturally, if you need your SoundFunc to be called, you cannot unroll.
Some changes has been made since earlier releases. One-shot sounds and
sounds with only one loop segment can now be played without using sample
interrupts. This is possible because one of the restrictions regarding the
AHISF_IMM
flag has been removed.
The AHISF_IMM
flag determines if AHI_SetSound()
,
AHI_SetFreq()
and AHI_SetVol()
should take effect immediately
or when the current sound has reached its end. The rules for this flags
are:
What does this mean? It means that if all you want to do is to play a
one-shot sound from inside a PlayerFunc, you can do that by first
calling AHI_SetSound()
, AHI_SetFreq()
and AHI_SetVol()
with AHISF_IMM
set, and then use AHI_SetSound(ch, AHI_NOSOUND,
0, 0, actrl, 0L)
to stop the sound when it has reached the end. You can
also set one loop segment this way.
AHI_PlayA()
was added in AHI version 4, and combines
AHI_SetSound()
, AHI_SetFreq()
and AHI_SetVol()
into
one tag-based function. It also allows you to set one loop and play
one-shot sounds.
To play a sound with more than one loop segment or ping-pong looping, a sample interrupt needs to be used. AHI's SoundFunc works like Paula's interrupts and is very easy to use.
The SoundFunc hook will be called with an AHIAudioCtrl
structure as object and an AHISoundMessage
structure as message.
ahism_Channel
indicates which channel caused the hook to be called.
An example SoundFunc which handles the repeat part of an instrument can look like this (SAS/C code):
__asm __saveds ULONG SoundFunc(register __a0 struct Hook *hook, register __a2 struct AHIAudioCtrl *actrl, register __a1 struct AHISoundMessage *chan) { if(ChannelDatas[chan->ahism_Channel].Length) AHI_SetSound(chan->ahism_Channel, 0, (ULONG) ChannelDatas[chan->ahism_Channel].Address, ChannelDatas[chan->ahism_Channel].Length, actrl, NULL); else AHI_SetSound(chan->ahism_Channel, AHI_NOSOUND, NULL, NULL, actrl, NULL); return NULL; }
This example is from an old version of the AHI NotePlayer for
DeliTracker 2. ChannelDatas
is an array where the start and
length of the repeat part is stored. Here, a repeat length of zero
indicates a one-shot sound. Note that this particular example only uses
one sound (0). For applications using multiple sounds, the sound number
would have to be stored in the array as well.
Once again, note that the AHISF_IMM
flag should never be
set in a SoundFunc hook!
Starting with V4, AHI_SetVol()
can take both negative volume and
pan parameters. If you set the volume to a negative value, the sample
will, if the audio mode supports it, invert each sample before playing. If
pan is negative, the sample will be encoded to go to the surround speakers.
The I/O request protocol makes it very easy to play audio streams, sounds from disk and non time-critical sound effects in a multitasking friendly way. Recoding is just as easy, on behalf of quality. Several programs can play sounds at the same time, and even record at the same time if your hardware is full duplex.
If you want to write a sample player, play (warning?) sounds in your applications, play an audio stream from a CD via the SCSI/IDE bus, write a voice command utility etc., this is the API to use.
Note that while all the low-level functions (see section Function Interface) count lengths and offsets in sample frames, the device interface--like all Amiga devices--uses bytes.
ahi.device
For High-level AccessFour primary steps are required to open ahi.device:
CreateMsgPort()
. Reply messages from
the device must be directed to a message port.
AHIRequest
using
CreateIORequest()
. CreateIORequest()
will initialize the I/O
request to point to your reply port.
ahi.device
unit AHI_DEFAULT_UNIT
or any other unit the
user has specified with, for example, a UNIT tooltype. Call
OpenDevice()
, passing the I/O request.
Each OpenDevice()
must eventually be matched by a call to
CloseDevice()
. When the last close is performed, the device will
deallocate all resources.
All I/O requests must be completed before CloseDevice()
. Abort any
pending requests with AbortIO()
.
Example:
struct MsgPort *AHImp = NULL; struct AHIRequest *AHIio = NULL; BYTE AHIDevice = -1; UBYTE unit = AHI_DEFAULT_UNIT; /* Check if user wants another unit here... */ if(AHImp = CreateMsgPort()) { if(AHIio = (struct AHIRequest *) CreateIORequest(AHImp, sizeof(struct AHIRequest))) { AHIio->ahir_Version = 4; if(!(AHIDevice = OpenDevice(AHINAME, unit, (struct IORequest *) AHIio, NULL))) { /* Send commands to the device here... */ if(! CheckIO((struct IORequest *) AHIio)) { AbortIO((struct IORequest *) AHIio); } WaitIO((struct IORequest *) AHIio); CloseDevice((struct IORequest *) AHIio); AHIDevice = -1; } DeleteIORequest((struct IORequest *) AHIio); AHIio = NULL; } DeleteMsgPort(AHImp); AHImp = NULL; }
You read from ahi.device
by passing an AHIRequest
to the
device with CMD_READ
set in io_Command
, the number of bytes
to be read set in io_Length
, the address of the read buffer set in
io_Data
, the desired sample format set in ahir_Type
and the
desired sample frequency set in ahir_Frequency
. The first read
command in a sequence should also have io_Offset
set to 0.
io_Length
must be an even multiple of the sample frame size.
To do double buffering, just fill the first buffer with DoIO()
and
io_Offset
set to 0, then start filling the second buffer with
SendIO()
using the same I/O request (but don't clear
io_Offset
!). After you have processed the first buffer, wait until
the I/O request is finished and start over with SendIO()
on the
first buffer.
The samples will automatically be converted to the sample format set in
ahir_Type
and to the sample frequency set in ahir_Frequency
.
Because it is quite unlikely that you ask for the same sample frequency the
user has chosen in the preference program, chances that the quality is
lower than expected are pretty high. The worst problem is probably the
anti-aliasing filter before the A/D converter. If the user has selected a
higher sampling/mixing frequency than you request, the signal will be
distorted according to the Nyquist sampling theorem. If, on the other
hand, the user has selected a lower sampling/mixing frequency than you
request, the signal will not be distorted but rather bandlimited more than
necessary.
You write to the device by passing an AHIRequest
to the device with
CMD_WRITE
set in io_Command
, the precedence in
io_Message.mn_Node.ln_Pri
, the number of bytes to be written in
io_Length
, the address of the write buffer set in io_Data
,
the sample format set in ahir_Type
, the desired sample frequency set
in ahir_Frequency
, the desired volume set in ahir_Volume
and
the desired stereo position set in ahir_Position
. Unless you are
doing double buffering, ahir_Link
should be set to NULL
.
io_Length
must be an even multiple of the sample frame size.
To do double buffering, you need two I/O requests. Create the second one
by making a copy of the request you used in OpenDevice()
. Start the
first with SendIO()
. Set ahir_Link
in the second request to
the address of the first request, and SendIO()
it. Wait on the
first, fill the first buffer again and repeat, this time with
ahir_Link
of the first buffer set to the address of the second I/O
request.
The problems with aliasing are present but not as obvious as with reading. Just make sure your source data is bandlimited correctly, and do not play samples at a lower frequency than they were recorded.
If you want to play several sounds at the same time, just make a new copy
of the I/O request you used in OpenDevice()
, and CMD_WRITE
it. The user has set the number of channels available in the preference
tool, and if too many requests are sent to the device the one with lowest
precedence will be muted. When a request is finished, the muted request
with the highest precedence will be played. Note that all muted requests
continue to play silently, so the programmer will not have to worry if
there are enough channels or not.
The precedences to use depend on what kind of sound you are playing. The
recommended precedences are the same as for audio.device
, listed in
AMIGA ROM Kernel Reference manual -- Devices. Reprinted without
permission. So sue me.
Precedences | Type of sound -------------+---------------------------------------------------------- 127 | Unstoppable. Sounds first allocated at lower | precedencies, then set to this highest level. 90 - 100 | Emergencies. Alert, urgent situation that requires | immediate action. 80 - 90 | Annunciators. Attention, bell (CTRL-G). 75 | Speech. Synthesized or recorded speech | (narrator.device). 50 - 70 | Sonic cues. Sounds that provide information that is not | provided by graphics. Only the beginning of of each sound | should be at this level; the rest should ne set to sound | effects level. -50 - 50 | Music program. Musical notes in a music-oriented program. | The higher levels should be used for the attack portions | of each note. -70 - -50 | Sound effects. Sounds used in conjunction with graphics. | More important sounds should use higher levels. -100 - -80 | Background. Theme music and restartable background sounds. -128 | Silence. Lowest level (freeing the channel completely is | preferred).
Right. As you can see, some things do not apply to ahi.device
.
First, there is no way to change the precedence of a playing sound, so the
precedences should be set from the beginning. Second, it is not
recommended to use the device interface to play music. However, playing an
audio stream from CD or disk comes very close. Third, there are no channels
to free in AHI since they are dynamically allocated by the device.
In this chapter some of the data types and structures used will be explained. For more information, please consult the autodocs and the include files.
Fixed
Fixed
is a signed long integer. It is used to represent decimal
numbers without using floating point arithmetics. The decimal point is
assumed to be in the middle of the 32 bit integer, thus giving 16 bits for
the integer part of the number and 16 bits for the fraction. The largest
number that can be stored in a Fixed
is +32767.999984741, and the
lowest number is -32768.
Example:
Decimal | Fixed --------+---------- 1.0 | 0x00010000 0.5 | 0x00008000 0.25 | 0x00004000 0 | 0x00000000 -0.25 | 0xffffc000 -0.5 | 0xffff8000 -1.0 | 0xffff0000
sposition
sposition
(stereo position) is a Fixed
, and is used to
represent the stereo position of a sound. 0 is far left, 0.5 is center and
1.0 is far right.
AHIUnitPrefs
And AHIGlobalPrefs
These structures are used in the AHIU
and AHIG
chunks,
respective, which are part of the settings file (`ENV:Sys/ahi.prefs'),
The file is read by AHI on each call to OpenDevice()
, just
before the audio hardware is allocated.
AHIUnitPrefs
specifies the audio mode and its parameters to use for
each device unit (currently 0-3 and AHI_NO_UNIT
; unit 0 is also called
AHI_DEFAULT_UNIT
).
AHIGlobalPrefs
contains some global options that can be used to
gain speed on slow CPUs, the global debug level and a protection against
CPU overload. The debug level specifies which of the functions in AHI
should print debugging information to the serial port (the output can be
redirected to a console window or a file with tools like Sushi
(1)).
Jump to: a - c - d - f - g - h - l - m - o - p - r - s - t - u - w
Jump to: a
Available from AmiNet, for example
ftp://ftp.germany.aminet.org/pub/aminet/dev/debug/Sushi.lha.
This document was generated on 12 September 1999 using the texi2html translator version 1.52.